/*
* Copyright 2011 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/
package org.etherframework.mediation.core
{
	import flash.utils.Dictionary;
	
	import org.etherframework.IEther;
	import org.etherframework.common.IStatefulDestroyable;
	import org.etherframework.mediation.BindingResult;
	import org.etherframework.mediation.IRUID;
	import org.etherframework.mediation.manager.IMediationManager;
	import org.flemit.reflection.MemberInfo;
	import org.floxy.IInterceptor;
	import org.floxy.IInvocation;
	
	[ExcludeClass]
	
	// TODO: Refactor.
	public final class Provider implements IInterceptor, IStatefulDestroyable
	{
		private static const EXCLUDED_DICT:Dictionary = getExcludedDictionary();
		
		private var _ether:IEther;
		private var _uid:String;
		private var _manager:IMediationManager;
		private var _mediationData:MediationData;
		private var _providerData:ProviderData;
		private var _destroyed:Boolean = false;
		
		private const _excluded:Dictionary = EXCLUDED_DICT;
		
		public function Provider(ether:IEther, manager:IMediationManager) 
		{
			_ether = ether;
			_uid = IRUID(ether).uid;
			_manager = manager;
			_mediationData = manager.mediationData;
			_providerData = manager.providerData;
		}
		
		// TODO: Refactor.
		public function intercept(invocation:IInvocation):void
		{	
			if (_destroyed || _excluded[invocation.method.name] || 
				((invocation.property && !invocation.arguments.length) && 
					!_mediationData.isBinding && !_mediationData.isExecuting))
			{
				if (invocation.canProceed)
					invocation.proceed();
				
				return;
			}
			
			const info:MemberInfo = invocation.property || invocation.method;
			const isGetter:Boolean = invocation.property && !invocation.arguments.length;
			const isSetter:Boolean = invocation.property && !isGetter;
			const id:String = _manager.getMediationId(_uid, _mediationData.context, 
				info.name, isGetter, isSetter);
			
			if (_mediationData.isExecuting && _manager.hasHandlers(_uid, id))
			{
				_mediationData.isExecuting = false;
				dispatchMediation(id, _uid, invocation, isGetter, isSetter);
				
				if (!(invocation.property && invocation.arguments.length))
				{
					const returnType:Class = invocation.method.returnType.classDefinition;
					// TODO: Check for proper return type in case of _mediationInfo.returnResult[0];
					invocation.returnValue = returnType == BindingResult ? 
						_mediationData.returnResult.clone() : 
						getClosestResult(_mediationData.returnResult);
				}
			}
			else if (_mediationData.isBinding) 
			{
				_providerData.clear();
				_providerData.uid = _uid;
				_providerData.isGetter = isGetter;
				_providerData.isSetter = isSetter;
				_providerData.providerName = invocation.targetType.name;
				_providerData.name = info.name;
			}
			else
			{
				if (invocation.canProceed)
					invocation.proceed();
				
				if (_manager.hasHandlers(_uid, id))
					dispatchMediation(id, _uid, invocation, isGetter, isSetter);
			}
		}
		
		private function dispatchMediation(type:String, uid:String, invocation:IInvocation, 
										   isGetter:Boolean, isSetter:Boolean):void
		{
			const data:* = invocation.property ? invocation.arguments : 
				((invocation.arguments.length) ? invocation.arguments : null);
			
			_providerData.setValue(type, data);
			_providerData.uid = uid;
			_providerData.name = invocation.method.name;
			_providerData.isGetter = isGetter;
			_providerData.isSetter = isSetter;
			_ether.dispatch(new MediationEvent(type));
			_providerData.clearValue(type);
		}
		
		private function getClosestResult(value:BindingResult):*
		{
			for (var i:int = 0; i < value.length; i++) 
			{
				if (value[i] !== null)
				{
					return value[i];
				}
			}
			return null;
		}
		
		public function destroy():void
		{
			_ether = null;
			_manager = null;
		}
		
		public function get ether():IEther { return _ether; }
		public function get destroyed():Boolean { return _destroyed; }
		public function set destroyed(value:Boolean):void { _destroyed = value; }
		
		private static function getExcludedDictionary():Dictionary
		{
			var result:Dictionary = new Dictionary();
			
			result["addEventListener"] = true;
			result["removeEventListener"] = true;
			result["hasEventListener"] = true;
			result["dispatchEvent"] = true;
			result["willTrigger"] = true;
			
			return result;
		}
	}
}